// Package lexer: 词法分析器定义包. 这个包定义了词法分析器struct以及一系列用于词法分析的方法
package lexer

import (
	"fmt"
	"strings"
	"z-lang/token"
)

// Lexer 词法分析器struct
type Lexer struct {
	input        string // 输入字符串（源代码）
	position     int    // 当前字符在输入字符串中的位置
	readPosition int    // 读取位置（指向当前字符所在位置的后一个字符）因为词法分析器需要进一步查看字符串中的下一个字符，方便进行词法分析
	ch           byte   // 当前字符 -> 这里是简单化情况下，只支持ASCII字符。因为byte只有一个字节，而Unicode、U8中，单个字符会占用
	//                               多字节，另外读取input中的字符时也不能直接取指定位置的字节了。如果想要支持Unicode、U8那这里需要改用rune TODO 这是一个可以改进的点
	lines      []string // 输入字符串按照\n切割后的数组
	curLineNum uint64   // 当前读取到的行号, 这个属性和上面的line属性搭配使用，目前主要用于提供更加友好的词法解析错误提示
}

// CurLineInfo 当前行信息
func (lex *Lexer) CurLineInfo() string {
	return fmt.Sprintf(" %d line '%s'", lex.curLineNum, lex.lines[lex.curLineNum-1])
}

// New 构造一个词法分析器struct并返回它的指针
func New(input string) *Lexer {
	lex := &Lexer{input: input, lines: strings.Split(input, "\n"), curLineNum: 1}
	// 初始化 position readPosition 让词法分析器准备就绪
	lex.readChar()
	return lex
}

// readChar 读取input中的下一个字符，并将position更新为刚刚使用过的readPosition，接着readPosition向前推进一位
//
//	让position永远指向当前字符所在的位置，readPosition永远指向当前字符的下一个字符
func (lex *Lexer) readChar() {
	if lex.readPosition >= len(lex.input) { // 到达了输入流的结尾
		lex.ch = 0
	} else {
		lex.ch = lex.input[lex.readPosition]
	}
	lex.position = lex.readPosition
	lex.readPosition++
}

// NextToken 根据Lexer当前指向的字符，生成对应的词法单元，返回一个词法单元Token struct
func (lex *Lexer) NextToken() token.Token {
	var tok token.Token
	// 跳过空白、回车、换行、制表等符号
	lex.skipWhiteSpace()
	// 处理字符
	switch lex.ch {
	// 处理符号
	case '=':
		// 遇见 = 号，那么先往后再看一个字符, 如果也是 = 那么说明它是 ==
		if lex.peekChar() == '=' {
			// 处理 ==
			curCh := lex.ch
			lex.readChar()
			tok.Literal = string(curCh) + string(lex.ch)
			tok.Type = token.EQ
		} else {
			// 处理 =
			tok = newToken(token.ASSIGN, lex.ch)
		}
	case '+':
		tok = newToken(token.PLUS, lex.ch)
	case '-':
		tok = newToken(token.MINUS, lex.ch)
	case '!':
		if lex.peekChar() == '=' {
			// 处理 !=
			curCh := lex.ch
			lex.readChar()
			literal := string(curCh) + string(lex.ch)
			tok = token.Token{Type: token.NOT_EQ, Literal: literal}
		} else {
			// 处理 !
			tok = newToken(token.BANG, lex.ch)
		}
	case '/':
		tok = newToken(token.SLASH, lex.ch)
	case '*':
		tok = newToken(token.ASTERISK, lex.ch)
	case '<':
		if lex.peekChar() == '<' {
			// 处理 << 位运算
			curCh := lex.ch
			lex.readChar()
			literal := string(curCh) + string(lex.ch)
			tok = token.Token{Type: token.LSHIFT, Literal: literal}
		} else {
			// 处理 < 符号
			tok = newToken(token.LT, lex.ch)
		}
	case '>':
		if lex.peekChar() == '>' {
			firstCh := lex.ch
			lex.readChar()
			if lex.peekChar() == '>' {
				// 处理 >>> 位运算
				secondCh := lex.ch
				lex.readChar()
				literal := string(firstCh) + string(secondCh) + string(lex.ch)
				tok = token.Token{Type: token.URSHIFT, Literal: literal}
			} else {
				// 处理 >> 位运算
				curCh := lex.ch
				literal := string(curCh) + string(lex.ch)
				tok = token.Token{Type: token.RSHIFT, Literal: literal}
			}
		} else {
			// 处理 > 符号
			tok = newToken(token.GT, lex.ch)
		}
	case ';':
		tok = newToken(token.SEMICOLON, lex.ch)
	case '(':
		tok = newToken(token.LPAREN, lex.ch)
	case ')':
		tok = newToken(token.RPAREN, lex.ch)
	case ',':
		tok = newToken(token.COMMA, lex.ch)
	case '{':
		tok = newToken(token.LBRACE, lex.ch)
	case '}':
		tok = newToken(token.RBRACE, lex.ch)
	case '"':
		tok.Type = token.STRING
		tok.Literal = lex.readString()
	case '[':
		tok = newToken(token.LBRACKET, lex.ch)
	case ']':
		tok = newToken(token.RBRACKET, lex.ch)
	case ':':
		tok = newToken(token.COLON, lex.ch)
	case '|':
		if lex.peekChar() == '|' {
			// 处理 || 逻辑或
			curCh := lex.ch
			lex.readChar()
			literal := string(curCh) + string(lex.ch)
			tok = token.Token{Type: token.LOGIC_OR, Literal: literal}
		} else {
			// 处理 | 或运算
			tok = newToken(token.OR, lex.ch)
		}
	case '&':
		if lex.peekChar() == '&' {
			// 处理 && 逻辑与
			curCh := lex.ch
			lex.readChar()
			literal := string(curCh) + string(lex.ch)
			tok = token.Token{Type: token.LOGIC_AND, Literal: literal}
		} else {
			// 处理 & 与运算
			tok = newToken(token.AND, lex.ch)
		}
	case '~':
		tok = newToken(token.NOT, lex.ch)
	case '^':
		tok = newToken(token.XOR, lex.ch)
	case 0:
		tok.Literal = ""
		tok.Type = token.EOF
	default:
		// 处理标识符或关键字。对于标识符或关键字，我们需要判断当前字符是不是字母
		//   如果是字母，那么我们需要继续往下读取其他字符，直到遇见非字母的字符为止
		//   读取完毕之后判断究竟是标识符还是关键字
		//   如果不是字母，那么说明这个字符是非法的
		if isLetter(lex.ch) {
			tok.Literal = lex.readIdentifier()
			tok.Type = token.LookupIdent(tok.Literal)
			tok.LineNum = lex.curLineNum
			return tok
		} else if isDigit(lex.ch) {
			tok.Literal = lex.readNumber()
			tok.Type = token.INT
			tok.LineNum = lex.curLineNum
			return tok
		} else {
			tok = newToken(token.ILLEGAL, lex.ch)
		}
	}
	tok.LineNum = lex.curLineNum
	lex.readChar()
	return tok
}

// readString 读取字符串数据
func (lex *Lexer) readString() string {
	start := lex.readPosition
	for {
		lex.readChar()
		if lex.ch == '"' || lex.ch == 0 {
			break
		}
	}
	return lex.input[start:lex.position]
}

// readIdentifier 读取标识符。当词法分析器遇到字母时，先记录下第一个字母的位置作为start
//
//	然后词法分析器不断向前推进，直到遇到第一个不是字母的字符为止，然后取
//	start 到 position 的切片
func (lex *Lexer) readIdentifier() string {
	var start = lex.position
	for isLetter(lex.ch) {
		lex.readChar()
	}
	return lex.input[start:lex.position]
}

// readNumber 读取数字。当词法分析器遇到数字时，先记录下第一个字符的位置作为start
//
//	然后词法分析器不断向前推进，直到遇到第一个不是数字的字符为止，然后取
//	start 到 position 的切片.
//	这里只是简单的判断了整数，所以这门语言是不支持小数、负数、浮点数、进制数的，TODO 这是一个可以改进的点
func (lex *Lexer) readNumber() string {
	var start = lex.position
	for isDigit(lex.ch) {
		lex.readChar()
	}
	return lex.input[start:lex.position]
}

// skipWhiteSpace 跳过空白、回车、换行、制表等字符。在这门语言中空白字符只用作词法单元之间的分隔符，其他的回车、换行、制表等根本没用上，，所以直接跳过就好了
//
//	当然对于某些语言而言，这些符号可能也是有其他特殊作用的 TODO 这是一个可以改进的点
func (lex *Lexer) skipWhiteSpace() {
	for lex.ch == ' ' || lex.ch == '\t' || lex.ch == '\n' || lex.ch == '\r' {
		if lex.ch == '\n' {
			lex.curLineNum++ // 当前行读取完毕，当前读取行加1
		}
		lex.readChar()
	}
}

// peekChar 获取当前字符的下一个字符，但是它不会推动position或readPosition
func (lex *Lexer) peekChar() byte {
	if lex.position >= len(lex.input) {
		return 0
	}
	return lex.input[lex.readPosition]
}

// newToken 传入Tokentype 和 字符 byte 创建一个 词法单元token.Token
func newToken(tokenType token.TokenType, ch byte) token.Token {
	return token.Token{Type: tokenType, Literal: string(ch)}
}

// isLetter 判断ch是不是字母或者 _ 符号 所以命名是允许 a_b 这种格式的
//
//	如果想要允许其他特殊符号命名比如 $ ! 之类的也可以通过修改这个函数来实现 TODO 这是一个可以改进的点
func isLetter(ch byte) bool {
	return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || (ch == '_')
}

// isDigit 判断ch是不是数字
func isDigit(ch byte) bool {
	return '0' <= ch && ch <= '9'
}
